﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MachineLearning {
    /// <summary>
    /// 
    /// </summary>
    abstract class NeuralNetworkInterface {
        public delegate void updateStatusEventHandler();
        public updateStatusEventHandler finishedProcessingEvent;

        protected SignalConditioner _sc = null;
        protected int _numInputs = 0;
        protected int _numOutputs = 0;
        protected NeuralNetwork _ann = null;
        public NeuralNetwork ANN {
            get { return _ann; }
        }

        //constructor for base ANN Interface class
        public NeuralNetworkInterface(SignalConditioner sc) {
            _sc = sc;
            _numInputs = _sc.InputConditioners.Count;
            _numOutputs = _sc.OutputConditioners.Count;
        }

        protected void newNetwork(int[] nodes, double eta, double alpha) {
            nodes[0] = _numInputs;
            nodes[nodes.Length - 1] = _numOutputs;
            //get a new ANN with learning rate eta and momentum alpha
            _ann = new NeuralNetwork(nodes, eta, alpha);
        }

        public void newNetworkFromFile(string fileName) {
            _ann = new NeuralNetwork(fileName);
        }

        public void setNetworkName(string name) {
            _ann.Name = name;
        }
    }

    //online classifier, used for runtime classification 
    class OnlineClassifier : NeuralNetworkInterface {

        protected double[] _finalOutput = null;

        public OnlineClassifier(SignalConditioner sc) : base(sc) { }

        protected void classifyInstance(Instance instance) {
            _finalOutput = new double[instance.InputValues.Length];
            _finalOutput = _ann.classify(_sc.conditionInput(instance.InputValues));
            //restore the datums to their original values
            _finalOutput = _sc.reclaim(_finalOutput);
        }
    }

    //online trainier, used for runtime training 
    class OnlineNetworkTrainer : OnlineClassifier {
        public OnlineNetworkTrainer(SignalConditioner sc) : base(sc) { }

    }

    //class for batch classification
    class BatchClassifier : NeuralNetworkInterface {
        public virtual int getCurrentEpoch() { return 1; } //a classifiers epoch is always 1
        public virtual void trainNetwork(int epochs, int[] nodes, double eta, double alpha) { }
        public virtual void validateNetwork() { }
        public virtual void trainAndValidate(int epochs, int[] nodes, double eta, double alpha) { }

        protected double[][] _finalOutputs = null;
        public double[][] FinalOutputs {
            get { return _finalOutputs; }
        }

        protected Instance[] _instances = null;
        public Instance[] Instances {
            get { return _instances; }
        }

        protected Instance[] _validationInstances = null;
        public Instance[] ValidationInstances {
            get { return _validationInstances; }
        }

        protected double _meanSquaredError = 0.0;
        protected double _validationMeanSquaredError = 0.0;

        protected bool _running = false;
        public bool Running {
            get { return _running; }
            set { _running = value; }
        }

        public BatchClassifier(SignalConditioner sc, Instance[] inst) : base(sc) {
            _instances = inst; //init the instances in the network interface
        }

        public BatchClassifier(SignalConditioner sc, Instance[] inst, Instance[] valid)
            : base(sc) {
            _instances = inst; //init the instances in the network interface
            _validationInstances = valid;
        }

        public string saveNetwork(string fileName) {
            return _ann.saveNetwork(fileName);
        }

        //classify a set of instances
        public void classifyInstances(Instance[] instances) {
            _running = true;
            double[][] tempIn = new double[_instances.Length][];
            for(int i = 0; i < _instances.Length; i++)
                tempIn[i] = _sc.conditionInput(_instances[i].InputValues);
            _finalOutputs = new double[instances.Length][];
            for (int i = 0; i < instances.Length; i++) {
                _finalOutputs[i] = _ann.classify(tempIn[i]);
                if (!_running) break; //stop the classification process
            }
            //restore the datums to their original values
            for (int i = 0; i < _finalOutputs.Length; i++)
                _finalOutputs[i] = _sc.reclaim(_finalOutputs[i]);
            _running = false;
            if (finishedProcessingEvent != null)
                finishedProcessingEvent.Invoke();
        }

        public virtual double getMeanSquaredError() {
            return _meanSquaredError / (double)_instances.Length;
        }

        public virtual double getValidationMeanSquaredError() {
            return _validationMeanSquaredError / (double)_validationInstances.Length;
        }
    }

    class BatchNetworkTrainer : BatchClassifier {

        private int _currentEpoch = 0;

        public BatchNetworkTrainer(SignalConditioner sc, TrainingExample[] tr) : base(sc, tr) { }

        public BatchNetworkTrainer(SignalConditioner sc, TrainingExample[] tr, TrainingExample[] valid) 
            : base(sc, tr, valid) { }

        //train the network
        public override void trainNetwork(int epochs, int[] nodes, double eta, double alpha) {
            _running = true;
            validateInput(nodes, eta, alpha);
            //train the network
            double[][] trainIn = new double[_instances.Length][];
            double[][] trainOut = new double[_instances.Length][];
            int index = 0;
            foreach (TrainingExample te in _instances) {
                trainIn[index] = _sc.conditionInput(te.InputValues);
                trainOut[index] = _sc.conditionOutput(te.OutputValues);
                index++;
            }
            for (int i = _currentEpoch; i < epochs; i++) {
                for (int j = 0; j < _instances.Length; j++)
                    _meanSquaredError += _ann.train(trainIn[j], trainOut[j]);
                _currentEpoch++;
                if (!_running) break; //stop the training process
            }
            classifyInstances(_instances);
        }




        //train the network
        public override void trainAndValidate(int epochs, int[] nodes, double eta, double alpha) {
            _running = true;
            validateInput(nodes, eta, alpha);
            //train the network
            double[][] trainIn = new double[_instances.Length][];
            double[][] trainOut = new double[_instances.Length][];
            double[][] validateIn = new double[_validationInstances.Length][];
            double[][] validateOut = new double[_validationInstances.Length][];
            int index = 0;
            for(int i = 0; i < _instances.Length; i++) {
                trainIn[index] = _sc.conditionInput(_instances[i].InputValues);
                trainOut[index] = _sc.conditionOutput(((TrainingExample)_instances[i]).OutputValues);
                validateIn[index] = _sc.conditionInput(_validationInstances[i].InputValues);
                validateOut[index] = _sc.conditionOutput(((TrainingExample)_validationInstances[i]).OutputValues);
                index++;
            }
            for (int i = _currentEpoch; i < epochs; i++) {
                for (int j = 0; j < _instances.Length; j++) {
                    _meanSquaredError += _ann.train(trainIn[j], trainOut[j]);
                    _validationMeanSquaredError += _ann.validate(validateIn[j], validateOut[j]);
                }
                _currentEpoch = i;
                if (!_running) break; //stop the training process
            }
            classifyInstances(_instances);
        }

        public override void validateNetwork() {
            _running = true;
            //train the network
            double[][] validateIn = new double[_validationInstances.Length][];
            double[][] validateOut = new double[_validationInstances.Length][];
            int index = 0;
            for (int i = 0; i < _instances.Length; i++) {
                validateIn[index] = _sc.conditionInput(_validationInstances[i].InputValues);
                validateOut[index] = _sc.conditionOutput(((TrainingExample)_validationInstances[i]).OutputValues);
                index++;
            }
            for (int j = 0; j < _instances.Length; j++) {
                _validationMeanSquaredError += _ann.validate(validateIn[j], validateOut[j]);
            }
            classifyInstances(_validationInstances);
        }

        //some messy logic for continuing a training cycle
        private void validateInput(int[] nodes, double eta, double alpha) {
            if (_ann == null || nodes.Length != _ann.Neurons.Count) {
                newNetwork(nodes, eta, alpha);
                _meanSquaredError = 0.0;
                _currentEpoch = 0;
            } else {
                bool createNewAnn = false;
                for (int i = 1; i < nodes.Length - 1; i++) {
                    if (nodes[i] != _ann.Neurons[i].Count)
                        createNewAnn = true;
                }
                if (createNewAnn) {
                    newNetwork(nodes, eta, alpha);
                    _meanSquaredError = 0.0;
                    _currentEpoch = 0;
                } else {
                    //update eta and alpha for new training round
                    _ann.Eta = eta; 
                    _ann.Alpha = alpha;
                }
            }
        }

        //get the current training epoch
        public override int getCurrentEpoch() {
            return _currentEpoch;
        }

        //get the mean squared error
        public override double getMeanSquaredError() {
            return _meanSquaredError / (double)((_currentEpoch) * _instances.Length);
        }
    }
}
